'use strict'

const http = require('http')
const fs  = require('fs')
const fsp = fs.promises

/** ---------------------解析Body数据----------------------------- */

/**
 * Content-Disposition: form-data; name="file"; filename="xxx"
 * Content-Type: text/plain
 * 
 * @param {string} headerData 
 */

function parseBodyHeaders (headerData) {
  let hlist = headerData.split('\r\n').filter(p => p.length > 0)

  //解析content-disposition => hlist[0]
  //按照; 切分数组，选出从第二个元素开始的数组
  let csplit = hlist[0].split('; ').slice(1)

  let filename
  let name
  for (let a of csplit) {
      if (a.indexOf('filename=') === 0) {
        filename = a.substring(a.indexOf('=')+2, a.length - 1)
      } else {
        name = a.substring(a.indexOf('=')+2, a.length - 1)
      }
  }

  let headers = {}
  let ind = 0
  let k
  for (let h of hlist) {
    ind = h.indexOf(':')
    k = h.substring(0, ind).toLowerCase()
    headers[k] = h.substring(ind+1).trim()
  }

  return {
    filename,
    name,
    headers,
    contentType: headers['content-type'] || ''
  }

}

function parseBody (bodyData, headers) {
  let ctype = headers['content-type']

  let bdy = ctype.substring( ctype.indexOf('=') + 1 )

  let crlf_bdy = `\r\n--${bdy}`

  let header_end_index = bodyData.indexOf('\r\n\r\n',
                                        crlf_bdy.length);

  let headerData = bodyData.toString('utf8',
                                    crlf_bdy.length, 
                                    header_end_index);

  //解析文件头信息
  let hd = parseBodyHeaders(headerData)

  let file = {
    start: header_end_index + 4,
    end: bodyData.indexOf(crlf_bdy, header_end_index),
    ...hd
  }

  file.length = file.end - file.start

  return file
}

//解析文件扩展名
function parseExtName (filename) {
  if(filename.length < 2) {
    return ''
  }

  let nsplit = filename.split('.').filter(p => p.length > 0) 
  if(nsplit.length < 2) return ''
  return `.${nsplit[ nsplit.length - 1 ]}`
}

/** ----------------------解析Body数据 - END ----------------------- */


let html = `
  <!DOCTYPE html>
  <html>
    <head>
      <title>First HTTP</title>
    </head>
    <body>
      你好。
    </body>
  </html>
`

let routerTable = {

  GET : {
      '/' : async (ctx) => {
        response.setHeader('content-type', 
                'text/html; charset=utf-8')

        ctx.end(html)
      },

      '/test' : async (ctx) => {
        let status = parseInt(Math.random() * 200) + 199
        ctx.response.statusCode = status
        ctx.end('test end')
      },

      '/upload' : async (ctx) => {
          let stm = fs.createReadStream('./upload.html')
          stm.pipe(ctx.response, {end: false})
          stm.on('end', () => {
            ctx.end()
          })
      },
  },

  POST : {
      '/data': async (ctx) => {

        let file = parseBody(ctx.rawBody, ctx.headers)

        console.log(file)

        if(!ctx.rawBody) {
          ctx.end();
          return
        }

        let fname = `${Date.now()}-${Math.random()}` + `${parseExtName(file.filename)}`;
        let fh = await fsp.open(fname, 'a+')
        let fret = await fh.write(ctx.rawBody, file.start, file.length, 0);

        //关闭打开的文件
        fh.close();
        
        ctx.end(`${fret.bytesWritten}`);
        //ctx.end(ctx.rawBody)
      }
  }


}


http.createServer(async(request, response) => {

  let rm = routerTable[ request.method ]

  let usplit = request.url.split('?')

  let pathname = usplit[0]
  let querystring = usplit[1] || ''

  if (!rm || !rm[ pathname ] ) {
    response.statusCode = 404
    response.end('page not found')
    return
  }

  //ctx 是 contenxt的缩写，意为请求上下文，就是一个打包了各种数据和方法的对象
  let ctx = {
    method: request.method,
    path: pathname,
    search: querystring,
    headers: request.headers,
    reuqest: request,
    response: response,
    end: (data, encoding = 'utf8') => {
      response.end(data, encoding)
    },
    write: (data, encoding = 'utf8') => {
      response.write(data, encoding)
    },
    totalLength: 0,
    rawBody: null,
    bufferList: []
  }

  await new Promise((rv,rj) => {
    if('PD'.indexOf(request.method[0]) >= 0) {
      request.on('data', chunk => {
        ctx.bufferList.push(chunk)
        ctx.totalLength += chunk.length
      })
    }else {
      request.on('data', data => {
        response.statusCode = 400
        response.end('bad request', () => {
          request.destroy()
        })
      })
    }

    let resolved = false;
    request.on('end', () => {
      resolved = true;
      rv();
    })

    request.on('close', () => {
      (!resolved) && rv();
    })
  })

  if(!response.writable || response.writableEnded) {
    return
  }

  if(ctx.bufferList.length > 0) {
    ctx.rawBody = Buffer.concat(ctx.bufferList, ctx.totalLength)
    ctx.bufferList = []
  }

  rm[ pathname ](ctx)

})
.listen(3456)
